using System;
using System.Data;
using System.Text;
using System.Globalization;

using BizElements.Core.ProviderComponents;

namespace BizElements.Core
{
	#region Documentation.
	/// <summary>
	/// Inserts a single row to a table or a view.
	/// </summary>
	/// <remarks>If a target table contains an autonumber field then the InsertStatement will try to 
	/// automatically retrieve the identity assigned to the inserted row.</remarks>
	/// <example>
	/// The following example inserts a new row into the Employees row and returns the new auto-identity value.
	/// <code>
	/// public int InsertEmployee(string firstName, string lastName)
	/// {
	/// 	EmployeesMeta employees = new EmployeesMeta();
	/// 	InsertStatement insert = new InsertStatement(employees);
	/// 	insert.AddExpression(employees.FirstName, firstName);
	/// 	insert.AddExpression(employees.LastName, lastName);
	/// 	
	/// 	// Execute method returns the auto-identity value assigned to the inserted row.
	/// 	int employeeId = insert.Execute();
	/// 	return employeeId;
	/// }
	/// </code>
	/// </example>
	#endregion
	[Serializable]
	public sealed class InsertStatement : SqlStatementBase
	{
		#region Members.
		
		InsertList columnsAndValues = new InsertList();

		#endregion

		#region Contructors.
	
		/// <summary>
		/// Creates a statement that inserts a single row.
		/// </summary>
		/// <param name="intoTable">The table that is to receive the data.</param>
		public InsertStatement(IDbTable intoTable) 
            : base(intoTable)
		{
        }

		#endregion

		#region Public methods.

		/// <summary>
		/// Adds a column-value pair to the insert list.
		/// </summary>
		/// <param name="column">Column. <b>Null</b> is not allowed.</param>
		/// <param name="value">Value that is inserted in the specified column. May be <b>null</b>.</param>
		public void AddExpression(IDbColumn column, object value)
		{
			if (column == null)
                throw new ArgumentNullException("column", Messages.InsertStatement_ColMayNoBeNull);

			this.columnsAndValues.Add(column, value);
		}

		/// <summary>
		/// Renders INSERT statement.
		/// Throws exception if the columns/values list is empty.
		/// </summary>
		/// <param name="dbms">Target DBMS.</param>
		/// <param name="output">StringBuilder to which SQL is appended.</param>
		/// <param name="parameters">SQL parameter collection to which the object's and its children's
		/// parameters are added. After the rendering is done the collection contains all parameters with unique names.</param>
        public override void Render(DbmsType dbms, StringBuilder output, DbParameterCollection parameters)
        {
            IInserter renderer = DbmsComponentFactory.GetComponent<IInserter>(dbms);
            DbParameter newId = new DbParameter("newId", DbType.Int32, null, ParameterDirection.Input);
            renderer.RenderInsert(this, newId, dbms, output, parameters);
        }

		/// <summary>
		/// Executes the INSERT command. Automatically generates the code that retrieves the new identity for 
		/// the supported databases. DBMS specific code depends on the DBMS property of the used ConnectionProvider.
		/// </summary>
		/// <returns>Autoidentity value for the inserted row. <b>null</b> if the table doesn't contain an 
		/// autoidentity field or the DBMS specific code cannot be generated.</returns>
		/// <remarks>Uses class' <see cref="IConnectionProvider"/> object.</remarks>
        public object Execute()
        {
            DbmsType dbms = this.ConnectionProvider.DBMS;
            IInserter executor = DbmsComponentFactory.GetComponent<IInserter>(dbms);
            CommandExecutionStatistics commandStats;
            object id = executor.Execute(this, dbms, this.ConnectionProvider, out commandStats);
            this.LastExecutedCommandInfo = commandStats;
            if (id == DBNull.Value)
                return null;
            else
                return id;
        }

        /// <summary>
		/// Executes the INSERT command. Automatically generates the code that retrieves the new identity for 
		/// the supported databases. DBMS specific code depends on the DBMS property of the used ConnectionProvider.
		/// </summary>
		/// <param name="conn"><see cref="IConnectionProvider"/> that will be used.</param>
		/// <returns>Autoidentity value for the inserted row. -1 if the table doesn't contain an 
		/// autoidentity field or the DBMS specific code cannot be generated.</returns>
		public object Execute(IConnectionProvider conn)
		{
			this.ConnectionProvider = conn;
			return Execute();
		}

		#endregion

		#region Properties.

		/// <summary>
		/// Gets the table that is to receive the data.
		/// </summary>
		public IDbTable Table
		{
			get {return TargetTable;}		
		}

		/// <summary>
		/// Gets the number of insert expressions in the InsertStatement. 
		/// </summary>
		public int ExpressionCount
		{
			get {return this.columnsAndValues.Count;}
		}

        /// <summary>
        /// Gets or sets the list of columns in which to insert data and the list of data values to be inserted.
        /// </summary>
        public InsertList ColumnsAndValues
        {
            get { return this.columnsAndValues; }
        }

		#endregion
	}
}
